/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_scene.cpp - scene management
//


#include "r_local.h"


/*
 ==============================================================================

 RENDER ENTITIES

 ==============================================================================
*/


/*
 ==================
 R_AddRenderEntity
 ==================
*/
int R_AddRenderEntity (scene_t *scene, const renderEntity_t *renderEntity){

	entity_t	*entity;

	if (scene->inUse)
		Com_Error(false, "R_AddRenderEntity: scene is currently in use");

	// Allocate a slot
	if (!scene->freeEntity){
		Com_Printf(S_COLOR_YELLOW "R_AddRenderEntity: no free entity slots\n");
		return 0;
	}

	entity = scene->freeEntity;

	scene->numEntities++;

	// Set up
	entity->active = true;

	entity->e = *renderEntity;

	// Link
	scene->freeEntity = scene->freeEntity->next;

	entity->prev = &scene->activeEntity;
	entity->next = scene->activeEntity.next;

	scene->activeEntity.next->prev = entity;
	scene->activeEntity.next = entity;

	return entity->index + 1;
}

/*
 ==================
 R_UpdateRenderEntity
 ==================
*/
void R_UpdateRenderEntity (scene_t *scene, int entityHandle, const renderEntity_t *renderEntity){

	entity_t	*entity;

	if (scene->inUse)
		Com_Error(false, "R_UpdateRenderEntity: scene is currently in use");

	if (entityHandle <= 0 || entityHandle > scene->maxEntities)
		Com_Error(false, "R_UpdateRenderEntity: entityHandle out of range");

	entity = &scene->entityList[entityHandle - 1];

	if (!entity->active){
		Com_Printf(S_COLOR_YELLOW "R_UpdateRenderEntity: entityHandle %i is not active\n", entityHandle);
		return;
	}

	// Development tool
	if (r_skipUpdates->integerValue)
		return;

	rg.pc.entityUpdates++;

	// Update
	entity->e = *renderEntity;
}

/*
 ==================
 R_RemoveRenderEntity
 ==================
*/
void R_RemoveRenderEntity (scene_t *scene, int entityHandle){

	entity_t	*entity;

	if (scene->inUse)
		Com_Error(false, "R_RemoveRenderEntity: scene is currently in use");

	if (entityHandle <= 0 || entityHandle > scene->maxEntities)
		Com_Error(false, "R_RemoveRenderEntity: entityHandle out of range");

	entity = &scene->entityList[entityHandle - 1];

	if (!entity->active){
		Com_Printf(S_COLOR_YELLOW "R_RemoveRenderEntity: entityHandle %i is not active\n", entityHandle);
		return;
	}

	// Remove all decals from the model
	R_RemoveDecals(scene, entityHandle);

	// No longer active
	entity->active = false;

	// Unlink
	entity->prev->next = entity->next;
	entity->next->prev = entity->prev;

	// Free the slot
	entity->next = scene->freeEntity;
	scene->freeEntity = entity;

	scene->numEntities--;
}


/*
 ==============================================================================

 RENDER LIGHTS

 ==============================================================================
*/


/*
 ==================
 R_AddRenderLight
 ==================
*/
int R_AddRenderLight (scene_t *scene, const renderLight_t *renderLight){

	light_t		*light;

	if (scene->inUse)
		Com_Error(false, "R_AddRenderLight: scene is currently in use");

	// Allocate a slot
	if (!scene->freeLight){
		Com_Printf(S_COLOR_YELLOW "R_AddRenderLight: no free light slots\n");
		return 0;
	}

	light = scene->freeLight;

	scene->numLights++;

	// Set up
	light->active = true;

	light->l = *renderLight;
	light->data.valid = false;

	// Link
	scene->freeLight = scene->freeLight->next;

	light->prev = &scene->activeLight;
	light->next = scene->activeLight.next;

	scene->activeLight.next->prev = light;
	scene->activeLight.next = light;

	return light->index + 1;
}

/*
 ==================
 R_UpdateRenderLight
 ==================
*/
void R_UpdateRenderLight (scene_t *scene, int lightHandle, const renderLight_t *renderLight){

	light_t		*light;

	if (scene->inUse)
		Com_Error(false, "R_UpdateRenderLight: scene is currently in use");

	if (lightHandle <= 0 || lightHandle > scene->maxLights)
		Com_Error(false, "R_UpdateRenderLight: lightHandle out of range");

	light = &scene->lightList[lightHandle - 1];

	if (!light->active){
		Com_Printf(S_COLOR_YELLOW "R_UpdateRenderLight: lightHandle %i is not active\n", lightHandle);
		return;
	}

	// Development tool
	if (r_skipUpdates->integerValue)
		return;

	rg.pc.lightUpdates++;

	// Update
	light->l = *renderLight;
	light->data.valid = false;
}

/*
 ==================
 R_RemoveRenderLight
 ==================
*/
void R_RemoveRenderLight (scene_t *scene, int lightHandle){

	light_t		*light;

	if (scene->inUse)
		Com_Error(false, "R_RemoveRenderLight: scene is currently in use");

	if (lightHandle <= 0 || lightHandle > scene->maxLights)
		Com_Error(false, "R_RemoveRenderLight: lightHandle out of range");

	light = &scene->lightList[lightHandle - 1];

	if (!light->active){
		Com_Printf(S_COLOR_YELLOW "R_RemoveRenderLight: lightHandle %i is not active\n", lightHandle);
		return;
	}

	// No longer active
	light->active = false;

	// Unlink
	light->prev->next = light->next;
	light->next->prev = light->prev;

	// Free the slot
	light->next = scene->freeLight;
	scene->freeLight = light;

	scene->numLights--;
}


/*
 ==============================================================================

 RENDER PARTICLES

 ==============================================================================
*/


/*
 ==================
 R_AddRenderParticle
 ==================
*/
int R_AddRenderParticle (scene_t *scene, const renderParticle_t *renderParticle){

	particle_t	*particle;

	if (scene->inUse)
		Com_Error(false, "R_AddRenderParticle: scene is currently in use");

	// Allocate a slot
	if (!scene->freeParticle){
		Com_Printf(S_COLOR_YELLOW "R_AddRenderParticle: no free particle slots\n");
		return 0;
	}

	particle = scene->freeParticle;

	scene->numParticles++;

	// Set up
	particle->active = true;

	particle->p = *renderParticle;

	// Link
	scene->freeParticle = scene->freeParticle->next;

	particle->prev = &scene->activeParticle;
	particle->next = scene->activeParticle.next;

	scene->activeParticle.next->prev = particle;
	scene->activeParticle.next = particle;

	return particle->index + 1;
}

/*
 ==================
 R_UpdateRenderParticle
 ==================
*/
void R_UpdateRenderParticle (scene_t *scene, int particleHandle, const renderParticle_t *renderParticle){

	particle_t	*particle;

	if (scene->inUse)
		Com_Error(false, "R_UpdateRenderParticle: scene is currently in use");

	if (particleHandle <= 0 || particleHandle > scene->maxParticles)
		Com_Error(false, "R_UpdateRenderParticle: particleHandle out of range");

	particle = &scene->particleList[particleHandle - 1];

	if (!particle->active){
		Com_Printf(S_COLOR_YELLOW "R_UpdateRenderParticle: particleHandle %i is not active\n", particleHandle);
		return;
	}

	// Development tool
	if (r_skipUpdates->integerValue)
		return;

	rg.pc.particleUpdates++;

	// Update
	particle->p = *renderParticle;
}

/*
 ==================
 R_RemoveRenderParticle
 ==================
*/
void R_RemoveRenderParticle (scene_t *scene, int particleHandle){

	particle_t	*particle;

	if (scene->inUse)
		Com_Error(false, "R_RemoveRenderParticle: scene is currently in use");

	if (particleHandle <= 0 || particleHandle > scene->maxParticles)
		Com_Error(false, "R_RemoveRenderParticle: particleHandle out of range");

	particle = &scene->particleList[particleHandle - 1];

	if (!particle->active){
		Com_Printf(S_COLOR_YELLOW "R_RemoveRenderParticle: particleHandle %i is not active\n", particleHandle);
		return;
	}

	// No longer active
	particle->active = false;

	// Unlink
	particle->prev->next = particle->next;
	particle->next->prev = particle->prev;

	// Free the slot
	particle->next = scene->freeParticle;
	scene->freeParticle = particle;

	scene->numParticles--;
}


/*
 ==============================================================================

 RENDER SCENES

 ==============================================================================
*/


/*
 ==================
 R_AllocScene
 ==================
*/
scene_t *R_AllocScene (bool primary){

	scene_t	*scene;
	int		i;

	// Find a free slot
	for (i = 0; i < MAX_RENDER_SCENES; i++){
		if (!rg.scenes[i])
			break;
	}

	if (i == MAX_RENDER_SCENES)
		Com_Error(false, "R_AllocScene: MAX_RENDER_SCENES hit");

	// Allocate the scene
	rg.scenes[i] = scene = (scene_t *)Mem_Alloc(sizeof(scene_t), TAG_RENDERER);

	// Fill it in
	scene->primary = primary;
	scene->size = 0;
	scene->inUse = false;
	scene->deferredFree = false;

	// Allocate and set up the entity list
	scene->numEntities = 1;
	scene->maxEntities = (primary) ? MAX_RENDER_ENTITIES : MAX_RENDER_ENTITIES >> 8;

	scene->entityList = (entity_t *)Mem_ClearedAlloc(scene->maxEntities * sizeof(entity_t), TAG_RENDERER);
	scene->size += scene->maxEntities * sizeof(entity_t);

	for (i = 0; i < scene->maxEntities; i++){
		scene->entityList[i].index = i;

		if (i < scene->maxEntities - 1)
			scene->entityList[i].next = &scene->entityList[i+1];
	}

	// Allocate and set up the light list
	scene->numLights = 0;
	scene->maxLights = (primary) ? MAX_RENDER_LIGHTS : MAX_RENDER_LIGHTS >> 8;

	scene->lightList = (light_t *)Mem_ClearedAlloc(scene->maxLights * sizeof(light_t), TAG_RENDERER);
	scene->size += scene->maxLights * sizeof(light_t);

	for (i = 0; i < scene->maxLights; i++){
		scene->lightList[i].index = i;

		if (i < scene->maxLights - 1)
			scene->lightList[i].next = &scene->lightList[i+1];
	}

	// Allocate and set up the particle list
	scene->numParticles = 0;
	scene->maxParticles = (primary) ? MAX_RENDER_PARTICLES : 0;

	if (!scene->maxParticles)
		scene->particleList = NULL;
	else {
		scene->particleList = (particle_t *)Mem_ClearedAlloc(scene->maxParticles * sizeof(particle_t), TAG_RENDERER);
		scene->size += scene->maxParticles * sizeof(particle_t);
	}

	for (i = 0; i < scene->maxParticles; i++){
		scene->particleList[i].index = i;

		if (i < scene->maxParticles - 1)
			scene->particleList[i].next = &scene->particleList[i+1];
	}

	// Allocate and set up the decal list
	scene->numDecals = 0;
	scene->maxDecals = (primary) ? MAX_RENDER_DECALS : 0;

	if (!scene->maxDecals)
		scene->decalList = NULL;
	else {
		scene->decalList = (decal_t *)Mem_ClearedAlloc(scene->maxDecals * sizeof(decal_t), TAG_RENDERER);
		scene->size += scene->maxDecals * sizeof(decal_t);
	}

	for (i = 0; i < scene->maxDecals; i++){
		scene->decalList[i].index = i;

		if (i < scene->maxDecals - 1)
			scene->decalList[i].next = &scene->decalList[i+1];
	}

	// Set up the active lists
	scene->activeEntity.prev = &scene->activeEntity;
	scene->activeEntity.next = &scene->activeEntity;

	scene->activeLight.prev = &scene->activeLight;
	scene->activeLight.next = &scene->activeLight;

	scene->activeParticle.prev = &scene->activeParticle;
	scene->activeParticle.next = &scene->activeParticle;

	scene->activeDecal.prev = &scene->activeDecal;
	scene->activeDecal.next = &scene->activeDecal;

	// Set up the free lists
	scene->freeEntity = scene->entityList;
	scene->freeLight = scene->lightList;
	scene->freeParticle = scene->particleList;
	scene->freeDecal = scene->decalList;

	// Set up the world entity (all scenes will have one)
	scene->worldEntity = scene->freeEntity;

	scene->worldEntity->e.type = RE_MODEL;
	scene->worldEntity->e.entityNum = -1;
	scene->worldEntity->e.origin.Clear();
	scene->worldEntity->e.axis.Identity();
	scene->worldEntity->e.model = NULL;
	scene->worldEntity->e.depthHack = false;
	scene->worldEntity->e.allowInView = VIEW_ALL;
	scene->worldEntity->e.allowShadowInView = VIEW_ALL;
	scene->worldEntity->e.material = NULL;
	scene->worldEntity->e.materialParms[MATERIALPARM_RED] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_GREEN] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_BLUE] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_ALPHA] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_TIMEOFFSET] = 0.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_DIVERSITY] = 0.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_MISC] = 0.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_MODE] = 0.0f;
	scene->worldEntity->e.soundEmitterHandle = 0;

	scene->freeEntity = scene->freeEntity->next;

	scene->worldEntity->prev = &scene->activeEntity;
	scene->worldEntity->next = scene->activeEntity.next;

	scene->activeEntity.next->prev = scene->worldEntity;
	scene->activeEntity.next = scene->worldEntity;

	// Clear the light cache
	Mem_Fill(scene->lightCache, 0, sizeof(scene->lightCache));

	return scene;
}

/*
 ==================
 R_FreeScene
 ==================
*/
void R_FreeScene (scene_t *scene){

	entity_t		*entity;
	decal_t			*decal;
	lightCache_t	*lightCache;
	lightNode_t		*lightNode, *next;
	int				i;

	// If the scene is currently in use, free it later
	if (scene->inUse){
		scene->deferredFree = true;
		return;
	}

	// Free the slot
	for (i = 0; i < MAX_RENDER_SCENES; i++){
		if (rg.scenes[i] == scene){
			rg.scenes[i] = NULL;
			break;
		}
	}

	// If this scene was used as primaryView, set primaryViewAvailable to false
	if (rg.primaryViewAvailable && rg.primaryView.scene == scene)
		rg.primaryViewAvailable = false;

	// Make sure decal data is freed
	for (decal = scene->activeDecal.next; decal != &scene->activeDecal; decal = decal->next){
		if (decal->baseIndices)
			Mem_Free(decal->baseIndices);

		Mem_Free(decal->indices);
		Mem_Free(decal->vertices);
	}

	// Make sure the light cache is freed
	for (i = 0; i < MAX_ENTITIES; i++){
		lightCache = scene->lightCache[i];

		if (!lightCache)
			continue;

		for (lightNode = lightCache->data.nodes; lightNode; lightNode = next){
			next = lightNode->next;

			Mem_Free(lightNode->surfaces);
			Mem_Free(lightNode);
		}

		Mem_Free(lightCache);
	}

	// Free the scene
	if (scene->entityList)
		Mem_Free(scene->entityList);
	if (scene->lightList)
		Mem_Free(scene->lightList);
	if (scene->particleList)
		Mem_Free(scene->particleList);
	if (scene->decalList)
		Mem_Free(scene->decalList);

	Mem_Free(scene);
}

/*
 ==================
 R_RenderScene
 ==================
*/
void R_RenderScene (scene_t *scene, const renderView_t *renderView, bool primaryView){

	int		timeFrontEnd;

	if (r_skipFrontEnd->integerValue)
		return;

	if (com_speeds->integerValue)
		timeFrontEnd = Sys_Milliseconds();

	// Don't allow this scene to be modified or freed until we're done with it
	scene->inUse = true;

	// Copy scene
	rg.scene = scene;

	// Copy render view
	rg.renderView = *renderView;

	// If a primary view
	if (primaryView){
		if (!rg.world)
			Com_Error(false, "R_RenderScene: NULL world");

		// Save the primary view
		rg.primaryViewAvailable = true;

		rg.primaryView.scene = rg.scene;
		rg.primaryView.renderView = rg.renderView;
	}

	// Render main view
	R_RenderView(primaryView, VIEW_MAIN);

	if (com_speeds->integerValue)
		com_timeFrontEnd += (Sys_Milliseconds() - timeFrontEnd);
}


// ============================================================================


/*
 ==================
 R_ClearScenes
 ==================
*/
void R_ClearScenes (){

	scene_t	*scene;
	int		i;

	for (i = 0; i < MAX_RENDER_SCENES; i++){
		if (!rg.scenes[i])
			continue;

		scene = rg.scenes[i];

		// Clear in-use flag
		scene->inUse = false;

		// If deferred, free it now
		if (scene->deferredFree)
			R_FreeScene(scene);
	}
}


/*
 ==============================================================================

 CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 R_ListRenderScenes_f
 ==================
*/
static void R_ListRenderScenes_f (){

	scene_t	*scene;
	int		count = 0, bytes = 0;
	int		i;

	Com_Printf("\n");
	Com_Printf("      -entities-- -lights---- -particles- -decals---- -size-\n");

	for (i = 0; i < MAX_RENDER_SCENES; i++){
		scene = rg.scenes[i];
		if (!scene)
			continue;

		count++;
		bytes += scene->size;

		Com_Printf("%4i: ", i);
		
		Com_Printf("%4i (%4i) ", scene->numEntities, scene->maxEntities);
		Com_Printf("%4i (%4i) ", scene->numLights, scene->maxLights);
		Com_Printf("%4i (%4i) ", scene->numParticles, scene->maxParticles);
		Com_Printf("%4i (%4i) ", scene->numDecals, scene->maxDecals);

		Com_Printf("%5ik\n", SIZE_KB(scene->size));
	}

	Com_Printf("------------------------------------------------------------\n");
	Com_Printf("%i total render scenes\n", count);
	Com_Printf("%.2f MB of render scene data\n", SIZE_MB_FLOAT(bytes));
	Com_Printf("\n");
}

/*
 ==================
 R_ListRenderEntities_f
 ==================
*/
static void R_ListRenderEntities_f (){

	scene_t		*scene;
	entity_t	*entity;
	int			slot;
	int			i;

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: listRenderEntities <scene>\n");
		return;
	}

	slot = Str_ToInteger(Cmd_Argv(1));
	if (slot < 0 || slot >= MAX_RENDER_SCENES){
		Com_Printf("Bad scene number %i\n", slot);
		return;
	}

	scene = rg.scenes[slot];
	if (!scene){
		Com_Printf("Scene number %i is not available\n", slot);
		return;
	}

	for (i = 0, entity = scene->entityList; i < scene->maxEntities; i++, entity++){
		if (!entity->active && entity != scene->worldEntity)
			continue;

		Com_Printf("%4i: ", entity->index);

		Com_Printf("(%7i, %7i, %7i) ", glqMath::FloatToInt(entity->e.origin.x), glqMath::FloatToInt(entity->e.origin.y), glqMath::FloatToInt(entity->e.origin.z));

		if (entity == scene->worldEntity){
			Com_Printf("WORLD  ");

			if (!scene->primary || !rg.world)
				Com_Printf("\n");
			else
				Com_Printf("%s\n", rg.world->name);
		}
		else {
			switch (entity->e.type){
			case RE_MODEL:
				Com_Printf("MODEL  ");

				if (entity->e.model == NULL)
					Com_Printf("\n");
				else
					Com_Printf("%s\n", entity->e.model->name);

				break;
			case RE_SPRITE:
				Com_Printf("SPRITE ");

				if (entity->e.material)
					Com_Printf("%s\n", entity->e.material->name);
				else
					Com_Printf("%s\n", rg.defaultMaterial->name);

				break;
			case RE_BEAM:
				Com_Printf("BEAM   ");

				if (entity->e.material)
					Com_Printf("%s\n", entity->e.material->name);
				else
					Com_Printf("%s\n", rg.defaultMaterial->name);

				break;
			default:
				Com_Printf("?????? ");

				Com_Printf("\n");

				break;
			}
		}
	}

	Com_Printf("--------------------\n");
	Com_Printf("%i active render entities\n", scene->numEntities);
}

/*
 ==================
 R_ListRenderLights_f
 ==================
*/
static void R_ListRenderLights_f (){

	scene_t	*scene;
	light_t	*light;
	int		slot;
	int		i;

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: listRenderLights <scene>\n");
		return;
	}

	slot = Str_ToInteger(Cmd_Argv(1));
	if (slot < 0 || slot >= MAX_RENDER_SCENES){
		Com_Printf("Bad scene number %i\n", slot);
		return;
	}

	scene = rg.scenes[slot];
	if (!scene){
		Com_Printf("Scene number %i is not available\n", slot);
		return;
	}

	for (i = 0, light = scene->lightList; i < scene->maxLights; i++, light++){
		if (!light->active)
			continue;

		Com_Printf("%4i: ", light->index);

		Com_Printf("(%7i, %7i, %7i) ", glqMath::FloatToInt(light->l.origin.x), glqMath::FloatToInt(light->l.origin.y), glqMath::FloatToInt(light->l.origin.z));

		switch (light->l.type){
		case RL_POINT:
			Com_Printf("POINT       ");
			break;
		case RL_CUBIC:
			Com_Printf("CUBIC       ");
			break;
		case RL_PROJECTED:
			Com_Printf("PROJECTED   ");
			break;
		case RL_DIRECTIONAL:
			Com_Printf("DIRECTIONAL ");
			break;
		default:
			Com_Printf("??????????? ");
			break;
		}

		if (light->l.material)
			Com_Printf("%s\n", light->l.material->name);
		else {
			if (light->l.type != RL_PROJECTED)
				Com_Printf("%s\n", rg.defaultLightMaterial->name);
			else
				Com_Printf("%s\n", rg.defaultProjectedLightMaterial->name);
		}
	}

	Com_Printf("--------------------\n");
	Com_Printf("%i active render lights\n", scene->numLights);
}

/*
 ==================
 
 ==================
*/
static void R_ListRenderParticles_f (){

}

/*
 ==================
 
 ==================
*/
static void R_ListRenderDecals_f (){

}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 R_InitScenes
 ==================
*/
void R_InitScenes (){

	// Add commands
	Cmd_AddCommand("listRenderScenes", R_ListRenderScenes_f, "Lists render scenes", NULL);
	Cmd_AddCommand("listRenderEntities", R_ListRenderEntities_f, "Lists render entities", NULL);
	Cmd_AddCommand("listRenderLights", R_ListRenderLights_f, "Lists render lights", NULL);
	Cmd_AddCommand("listRenderParticles", R_ListRenderParticles_f, "Lists render particles", NULL);
	Cmd_AddCommand("listRenderDecals", R_ListRenderDecals_f, "Lists render decals", NULL);
}

/*
 ==================
 R_ShutdownScenes
 ==================
*/
void R_ShutdownScenes (){

	// Remove commands
	Cmd_RemoveCommand("listRenderScenes");
	Cmd_RemoveCommand("listRenderEntities");
	Cmd_RemoveCommand("listRenderLights");
	Cmd_RemoveCommand("listRenderParticles");
	Cmd_RemoveCommand("listRenderDecals");
}